
package com.facebook.react.uimanager;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
import android.provider.Settings;
import androidx.annotation.Nullable;
import android.text.Editable;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.RelativeLayout;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.textinput.ReactEditText;

import java.util.HashMap;
import java.util.Map;

public class RNCustomKeyboardModule extends ReactContextBaseJavaModule {
    private static final String TAG = "RNCustomKeyboardModule";
    private static final int DEFAULT_TIMEOUT = 200;
    private static final int RETRY_COUNT = 5;
    private final int TAG_ID = 0xdeadbeaf;
    private int installRetryRef = 0;
    private final ReactApplicationContext reactContext;
    public static ReactInstanceManager rnInstanceManager;

    private String mDefaultInputMethodPkg;

    private ReactRootView rootView = null;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    private Map<Integer, Integer> mKeyboardToMaxInputLength = new HashMap<>();

    public RNCustomKeyboardModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        readDefaultInputMethodPkgName(reactContext);
    }
    
    private void readDefaultInputMethodPkgName(ReactApplicationContext context) {
        if (context != null) {
            String mDefaultInputMethodCls = Settings.Secure.getString(
                                                                      context.getContentResolver(),
                                                                      Settings.Secure.DEFAULT_INPUT_METHOD);
            //输入法类名信息
            if (!TextUtils.isEmpty(mDefaultInputMethodCls)) {
                //输入法包名
                mDefaultInputMethodPkg = mDefaultInputMethodCls.split("/")[0];
                Log.d(TAG, "mDefaultInputMethodPkg=" + mDefaultInputMethodPkg);
            }
        }
    }
    
    private ReactEditText getEditById(int id) throws IllegalViewOperationException{
        UIViewOperationQueue uii = this.getReactApplicationContext().getNativeModule(UIManagerModule.class).getUIImplementation().getUIViewOperationQueue();
        return (ReactEditText) uii.getNativeViewHierarchyManager().resolveView(id);
    }

    private void displayKeyboard(final Activity activity, final ReactEditText edit) {
        View keyboard = (View) edit.getTag(TAG_ID);
        if (keyboard.getParent() == null && edit.isFocused()) {
            ViewGroup.LayoutParams lp = rootView.getLayoutParams();
            WritableMap params = Arguments.createMap();
            WritableMap coordinates = Arguments.createMap();
            coordinates.putDouble("width", PixelUtil.toDIPFromPixel(lp.width));
            coordinates.putDouble("height", PixelUtil.toDIPFromPixel(lp.height));
            params.putMap("endCoordinates", coordinates);
            sendEvent("keyboardWillShow", params);

            activity.addContentView(keyboard, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        }
    }

    private void showKeyboard (final Activity activity, final ReactEditText edit) {
        final ResultReceiver receiver = new ResultReceiver(mHandler) {
            @Override
            protected void onReceiveResult(int resultCode, Bundle resultData) {
                displayKeyboard(activity, edit);
            }
        };

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                InputMethodManager im = ((InputMethodManager) getReactApplicationContext().getSystemService(Activity.INPUT_METHOD_SERVICE));
                if (!im.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS, receiver)) {
                    displayKeyboard(activity, edit);
                }
            }
        });
    }

    private void sendEvent(String eventName, @Nullable WritableMap prams) {
        ReactContext reactContext = getReactApplicationContext();
        if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
            reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, prams);
        }
    }

    private void sendFocusChangeListener (ReactEditText editText, boolean hasFocus) {
        if (editText != null) {
            EventDispatcher eventDispatcher =
                    reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
            if (hasFocus) {
                eventDispatcher.dispatchEvent(
                        new ReactTextInputFocusEvent(
                                editText.getId()));
            } else {
                eventDispatcher.dispatchEvent(
                        new ReactTextInputBlurEvent(
                                editText.getId()));

                eventDispatcher.dispatchEvent(
                        new ReactTextInputEndEditingEvent(
                                editText.getId(),
                                editText.getText().toString()));
            }
        }
    }

    private void setEditTextTagAndListener (final ReactEditText edit, final int tag, final String type) {
        final Activity activity = getCurrentActivity();
        if (edit == null || activity == null) {
            Log.e(TAG, "setEditTextListener error null, edit=" + edit);
            return;
        }

        edit.setShowSoftInputOnFocus(false); // 禁止EditText弹出软件盘，光标依然正常显示
        edit.setTag(TAG_ID, createCustomKeyboard(activity, tag, type));
        edit.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(final View v, boolean hasFocus) {
                Log.i(TAG, "onFocusChange hasFocus=" + hasFocus);
                sendFocusChangeListener(edit, hasFocus);
                if (hasFocus) {
                    showKeyboard(activity, edit);
                } else {
                    mHandler.removeCallbacksAndMessages(null);
                    View keyboard = (View) edit.getTag(TAG_ID);
                    if (keyboard.getParent() != null) {
                        sendEvent("keyboardWillHide", null);
                        ((ViewGroup) keyboard.getParent()).removeView(keyboard);
                    }
                }
            }
        });
    }

    @ReactMethod
    public void install(final int tag, final String type, final int maxInputLength, final int height) {
        installRetryRef = 0;
        mKeyboardToMaxInputLength.put(tag, maxInputLength);
        mHandler.post(new Runnable() {
            @Override
            public void run() {
               doInstall(tag, type);
            }
        });
    }

    private void doInstall (final int tag, final String type) {
        try {
            ReactEditText edit = getEditById(tag);
            setEditTextTagAndListener(edit, tag, type);
            installRetryRef = 0;
        } catch (IllegalViewOperationException e) {
            if (installRetryRef < RETRY_COUNT) {
                installRetryRef++;
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        doInstall(tag, type);
                    }
                }, DEFAULT_TIMEOUT);
            }
        }
    }

    private View createCustomKeyboard(Activity activity, int tag, String type) {
        RelativeLayout layout = new RelativeLayout(activity);
        rootView = new ReactRootView(this.getReactApplicationContext());

        Bundle bundle = new Bundle();
        bundle.putInt("tag", tag);
        bundle.putString("type", type);
        rootView.startReactApplication(
                rnInstanceManager,
                "CustomKeyboard",
                bundle);

        final float scale = activity.getResources().getDisplayMetrics().density;
        RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Math.round(216*scale));
        lParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
        layout.addView(rootView, lParams);
        return layout;
    }

    @ReactMethod
    public void uninstall(final int tag) {
        installRetryRef = 0;
        mHandler.removeCallbacksAndMessages(null);
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                final Activity activity = getCurrentActivity();
                
                try {
                    final ReactEditText edit = getEditById(tag);
                    if (edit == null) {
                        return;
                    }
                    
                    edit.setTag(TAG_ID, null);
                } catch (IllegalViewOperationException e) {
                    
                }
            }
        });
    }

    @ReactMethod
    public void getSelectionRange(final int tag, final Callback callback) {
        WritableMap responseMap = Arguments.createMap();
        try {
            ReactEditText edit = getEditById(tag);
            int start = Math.max(edit.getSelectionStart(), 0);
            int end = Math.max(edit.getSelectionEnd(), 0);
            Editable text = edit.getText();
            if (text != null) {
                responseMap.putString("text", text.toString());
                responseMap.putInt("start", start);
                responseMap.putInt("end", end);
            } else {
                Log.e(TAG, "getSelectionRange text=null");
                responseMap.putNull("text");
            }
            callback.invoke(responseMap);
        } catch (Exception e) {
            Log.e(TAG, "getSelectionRange error=" + e.getMessage());
            responseMap.putNull("text");
            callback.invoke(responseMap);
        }
    }

    @ReactMethod
    public void insertText(final int tag, final String text) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    final Activity activity = getCurrentActivity();
                    final ReactEditText edit = getEditById(tag);
                    if (edit == null) {
                        return;
                    }

                    Integer maxLength = mKeyboardToMaxInputLength.get(tag);
                    if (maxLength != null && edit.getText().length() >= maxLength ){
                        return;
                    }

                    int start = Math.max(edit.getSelectionStart(), 0);
                    int end = Math.max(edit.getSelectionEnd(), 0);
                    edit.getText().replace(Math.min(start, end), Math.max(start, end),
                            text, 0, text.length());
                } catch (Exception e) {
                    
                }
            }
        });
    }

    @ReactMethod
    public void backSpace(final int tag) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    final Activity activity = getCurrentActivity();
                    final ReactEditText edit = getEditById(tag);
                    if (edit == null) {
                        return;
                    }

                    int start = Math.max(edit.getSelectionStart(), 0);
                    int end = Math.max(edit.getSelectionEnd(), 0);
                    if (start != end) {
                        edit.getText().delete(start, end);
                    } else if (start > 0) {
                        edit.getText().delete(start - 1, end);
                    }
                } catch (Exception e) {
                    
                }
            }
        });
    }

    @ReactMethod
    public void doDelete(final int tag) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    final ReactEditText edit = getEditById(tag);
                    if (edit == null) {
                        return;
                    }

                    int start = Math.max(edit.getSelectionStart(), 0);
                    int end = Math.max(edit.getSelectionEnd(), 0);
                    if (start != end) {
                        edit.getText().delete(start, end);
                    } else if (start > 0) {
                        edit.getText().delete(start, end + 1);
                    }
                } catch (Exception e) {
                    
                }
            }
        });
    }

    @ReactMethod
    public void moveLeft(final int tag) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    final Activity activity = getCurrentActivity();
                    final ReactEditText edit = getEditById(tag);
                    if (edit == null) {
                        return;
                    }

                    int start = Math.max(edit.getSelectionStart(), 0);
                    int end = Math.max(edit.getSelectionEnd(), 0);
                    if (start != end) {
                        edit.setSelection(start, start);
                    } else {
                        edit.setSelection(start - 1, start - 1);
                    }
                } catch (Exception e) {
                    
                }
            }
        });
    }

    @ReactMethod
    public void moveRight(final int tag) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    final Activity activity = getCurrentActivity();
                    final ReactEditText edit = getEditById(tag);
                    if (edit == null) {
                        return;
                    }

                    int start = Math.max(edit.getSelectionStart(), 0);
                    int end = Math.max(edit.getSelectionEnd(), 0);
                    if (start != end) {
                        edit.setSelection(end, end);
                    } else if (start > 0) {
                        edit.setSelection(end + 1, end + 1);
                    }
                } catch (Exception e) {
                    
                }
            }
        });
    }

    @ReactMethod
    public void switchSystemKeyboard(final int tag) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    final Activity activity = getCurrentActivity();
                    final ReactEditText edit = getEditById(tag);
                    if (edit == null) {
                        return;
                    }

                    View keyboard = (View) edit.getTag(TAG_ID);
                    if (keyboard.getParent() != null) {
                        ((ViewGroup) keyboard.getParent()).removeView(keyboard);
                    }
                    mHandler.post(new Runnable() {

                        @Override
                        public void run() {
                            ((InputMethodManager) getReactApplicationContext().getSystemService(Activity.INPUT_METHOD_SERVICE)).showSoftInput(edit, InputMethodManager.SHOW_IMPLICIT);
                        }
                    });
                } catch (Exception e) {
                    
                }
            }
        });
    }

    @Override
    public String getName() {
      return "CustomKeyboard";
    }
}
